package lucene.index
{
	import flash.filesystem.File;
	
	import lucene.LuceneError;
	import lucene.file.FileHandler;
	import lucene.search.Similarity;
	
	public class SegmentInfo
	{
		
		public static const FORMAT_SINGLE_NORM_FILE:int = -3;
		public static const FORMAT_SHARED_DOC_STORE:int = -4;
		public static const FORMAT_HAS_PROX:int = -7;
		
		private var _directory:File
		private var _name:String;
		private var _docCount:int;
		private var _hasSingleNormFile:Boolean;
		private var _delGen:Number;
		private var _isCompound:int;
		
		private var _segFiles:Array = new Array();
		private var _segFilesSize:Array = new Array();
		
		private var _termDictionnary:Array;
		private var _termDictionnaryInfo:Array;
		
		private var _fields:Array = new Array();
		private var _norms:Array = new Array();
		private var _fieldsDicPositions:Array = new Array();
		
		private var _termInfoCache:Array = new Array();
		
		
		public function getCompoundFile(extension:String):FileHandler 
		{
			var fileName:String = this._name + extension;
			var fh:FileHandler;
			
			//trace ('asked filed : '+fileName);
			//trace ('isCompound ? : '+this._isCompound);
			
			if (!this._isCompound) {
				//trace('not compound');
				fh = new FileHandler(this._directory.resolvePath(this._name + extension));
			}
			
		    //trace ('filename : ' + fileName);
		    //trace ('from array : ');
		    //trace ("segFiles["+fileName+"] "+this._segFiles[fileName]);

			if (undefined != this._segFiles[fileName]) {
				//trace ('compound');
				fh = new FileHandler(this._directory.resolvePath(this._name + '.cfs'));
				fh.seek(this._segFiles[fileName]);				
			}
			else {
				throw new LuceneError('Compound file doesn\'t exist');
			}
			
			return fh;
		}
		
		private function _loadDictionnaryIndex():void
		{
			trace ('***---*** DictionnaryLoad ***---***');
						
			var tiiReader:FileHandler = this.getCompoundFile(".tii");
			
			var tiiVersion:int = tiiReader.readUInt32();
			trace (tiiVersion);

			// check version 
			if (tiiVersion != SegmentInfo.FORMAT_SINGLE_NORM_FILE && // v2.1 v2.2 
				tiiVersion != SegmentInfo.FORMAT_SHARED_DOC_STORE && // v2.3
				tiiVersion != SegmentInfo.FORMAT_HAS_PROX) {		 // v2.4
				throw new Error ('unsupported version');
			}
			
			var termCount:Number = tiiReader.readUInt64();
			trace (termCount);

			var indexInterval:int = tiiReader.readUInt32();
			//trace (indexInterval);

			var skipInterval:int = tiiReader.readUInt32();
			trace (skipInterval);

			
			// only for 2.1+
			if (tiiVersion <= SegmentInfo.FORMAT_SINGLE_NORM_FILE) {
				var maxSkipInterval:int = tiiReader.readUInt32();
				trace (maxSkipInterval);
			}
			
			var prevTerm:String = '';
			var docFreq:Number = 0;
			var freqDelta:Number = 0;
			var proxDelta:Number = 0;
			var indexDelta:Number = 0;
			this._termDictionnary = new Array();
			this._termDictionnaryInfo = new Array();
			for (var i:Number=0; i<termCount; i++) {
				trace ('for');
				var prefixLength:Number = tiiReader.readVInt();
				trace (prefixLength);
				var suffix:String = tiiReader.readString();
				trace (suffix);
				var termValue:String = lucene.index.Term.getPrefix(prevTerm, prefixLength).concat(suffix);
				trace (termValue);
				var fieldNumber:Number = tiiReader.readVInt();
				trace (fieldNumber)
				docFreq = tiiReader.readVInt();
				trace (docFreq);
				freqDelta += tiiReader.readVInt();
				trace (freqDelta);
				proxDelta += tiiReader.readVInt();
				trace (proxDelta);
				var skipDelta:Number = 0;

				if (int(docFreq) >= skipInterval) {
					skipDelta = tiiReader.readVInt();	
				}
				trace (skipDelta);
				
				indexDelta += tiiReader.readVInt();
				trace (indexDelta);
				
				this._termDictionnary.push([fieldNumber, termValue]);
				this._termDictionnaryInfo.push([docFreq, freqDelta, proxDelta, skipDelta, indexDelta]);
								
				prevTerm = termValue;
				
			}
			
			trace ('***---*** DictionnaryLoad ***---***');
						
		}
		
		//private function _getFieldPosition(fieldNum:String):int
		private function _getFieldPosition(fieldNum:int):int
		{
			return (this._fieldsDicPositions[fieldNum] != undefined ? this._fieldsDicPositions[fieldNum] : -1); 
		}		
		
		public function getFieldNum(fieldName:String):int {
			for each (var field:* in this._fields) {
			//for(var i:Number=0; i<this._fields.length; i++) {
				//var field:* = this._fields[i];
				if (field.fieldName == fieldName) {
					return field.number;
				}
			}
			
			return -1;
		}
	
		public function SegmentInfo(directory:File, name:String, docCount:int, delGen:Number = 0, hasSingleNormFile:Boolean = false, isCompound:int = 0)
		{
			this._directory = directory;
			this._name = name;
			this._docCount = docCount;
			this._hasSingleNormFile = hasSingleNormFile;
			this._delGen = delGen;
			
			this._termDictionnary = null;
			
			if (0 !== isCompound) {
				this._isCompound = isCompound;
			}
			else {
				// It's a pre-2.1 segment or isCompound is set to 'unknown'
            	// Detect if segment uses compound file
			}
			
			if (this._isCompound) {
				var cfsFile:FileHandler = new FileHandler(this._directory.resolvePath(this._name + '.cfs'));
				var segFilesCount:Number = cfsFile.readVInt();
				
				var fileName:String = '';
				var count:Number;
				var dataOffset:Number;
				for (count = 0; count<segFilesCount; count++) {
					dataOffset = cfsFile.readUInt64();
					
					if (0 != count) {
						this._segFilesSize[fileName] = dataOffset - this._segFiles[fileName]; // minus last entry 
					}
					fileName = cfsFile.readString();
					this._segFiles[fileName] = dataOffset;
				}
				if (0 != count) {
					this._segFilesSize[fileName] = cfsFile.length() - dataOffset;
				}
			}
			
			var fnmFile:FileHandler = this.getCompoundFile('.fnm');
			var fieldsCount:Number = fnmFile.readVInt();
			var fieldNames:Array = new Array();
			//var fieldNums:Array = new Array();
			
			for (count = 0; count<fieldsCount; count++) {
				var fieldName:String = fnmFile.readString();
				var fieldBits:int = fnmFile.readByte();
				
				// fieldInfo
				this._fields[count] = {'fieldName':fieldName, 'isIndexed':fieldBits & 1, 'number':count, 'storeTermVector':fieldBits & 2};
				
				if (fieldBits & 0x10) {
					// norms are omitted for indexed field
					var normArray:Array = new Array();
					var pad_size:Number = this._docCount;
					while (pad_size) {
						normArray.push(String.fromCharCode(Similarity.encodeNorm(1.0)));
						pad_size--;
					}
					this._norms[count] = normArray.join();
				}
				
				//fieldNums[count] = count;
				fieldNames[count] = fieldName;
				
			}
			
			trace ("********************************");
			trace (fieldNames);
			
			var tmpArray:Array = new Array();
			var fieldNums:Array = new Array();
			var i:Number; 
			for (i=0; i<fieldNames.length; i++) {
				tmpArray[fieldNames[i]] = i;
			}

			fieldNames.sort();

			for (i=0; i<fieldNames.length; i++) {
				fieldNums[tmpArray[fieldNames[i]]] = i;
			}
			
			for (i=0; i<fieldNums.length; i++) {
				this._fieldsDicPositions[fieldNums[i]] = i;
			} 
			trace (fieldNames);
			trace (this._fieldsDicPositions);
			trace ("********************************");
			//trace (fieldNums);
			
			/**
			 * @todo manage deleted docs
			 */
			
		}
		
		public function count():int
		{
			return this._docCount;
		}		
		
		public function getTermInfo(t:Term):*
		{
			trace ("---------------------------------------------");
			trace ("----------------getTermInfo------------------");
			trace ("---------------------------------------------");
			trace (this._name);
			
			var termKey:String = t.key();
			var termInfo:*
			if (this._termInfoCache[termKey] != undefined) {
				termInfo = this._termInfoCache[termKey];
				//this._termInfoCache.splice(this._termInfoCache.indexOf(termInfo), 1);
				
				return termInfo;
			}
			
			if(this._termDictionnary === null) {
				this._loadDictionnaryIndex();
			}
			
			var searchField:int = this.getFieldNum(t.field);
			
			if (-1 == searchField) {
				return null;
			}
			trace ("searchField "+searchField);
			
			var searchDicField:int = this._getFieldPosition(searchField);
			trace ("term.text "+t.text);
			trace ("searchDicField "+searchDicField);
			
			var lowIndex:Number = 0;
			var highIndex:Number = this._termDictionnary.length - 1;
			trace ("highIndex "+highIndex);
			while (highIndex >= lowIndex) {
				var mid:Number = (highIndex + lowIndex) >> 1;
				trace ("mid "+mid);
				var midTerm:Array = this._termDictionnary[mid];
				
				//var fieldNum:Number = midTerm.field;//this._getFieldPosition(midTerm[0]);
				var fieldNum:Number = this._getFieldPosition(midTerm[0]);
				trace ("fieldNum "+fieldNum);
				var delta:Number = searchDicField - fieldNum;
				trace ("delta "+delta);
				
				if (delta == 0) {
					trace ("delta "+delta);
					delta = t.text.localeCompare(midTerm[1]);
					trace (midTerm[1]);
				}
				trace ("delta "+delta);
				
				if (delta < 0) {
					highIndex = mid - 1;
				}
				else if (delta > 0) {
					lowIndex = mid + 1;
				}
				else {
					var a:Array = this._termDictionnaryInfo[mid];
					
					trace ("termInfo (array)");
					trace (a);
					// termInfo
					termInfo = {'docFreq':a[0], 'freqPointer':a[1], 'proxPointer':a[2], 'skipOffset':a[3], 'indexPointer':a[4]};
					
					this._termInfoCache[termKey] = termInfo;
					
					return termInfo;
				}
				
				trace ("highIndex "+highIndex);
				trace ("lowIndex "+lowIndex);
				
			}
			
			if (highIndex == -1) {
				return null;
			}
			
			trace ("passe ici");
			
			var prevPosition:Number = highIndex;
			trace ("prevPosition : "+prevPosition);
			var prevTerm:Array = this._termDictionnary[prevPosition];
			trace ("prevTerm : ");
			trace (prevTerm);
			var prevTermInfo:Array = this._termDictionnaryInfo[prevPosition];
			trace ("prevTermInfo : ");
			trace (prevTermInfo);

			var tisFile:FileHandler = this.getCompoundFile(".tis");
			//trace ("filePosition "+tisFile.position);

			var tisVersion:int = tisFile.readUInt32();
			
			// check version
			if (tisVersion != SegmentInfo.FORMAT_SINGLE_NORM_FILE &&
				tisVersion != SegmentInfo.FORMAT_SHARED_DOC_STORE &&
				tisVersion != SegmentInfo.FORMAT_HAS_PROX
				)
				throw new Error('wrong format version');
			
			var termCount:Number = tisFile.readUInt64();
			trace (termCount);
			var indexInterval:int = tisFile.readUInt32();
			trace (indexInterval)
			var skipInterval:int = tisFile.readUInt32();
			trace (skipInterval);
			
			// 2.1+ only
			if (tisVersion <= SegmentInfo.FORMAT_SINGLE_NORM_FILE) {
				var maxSkipLevel:int = tisFile.readUInt32();
				trace (maxSkipLevel);
			}
			
			// minus header size
			var seekpos:int = prevTermInfo[4] - (tisVersion <= SegmentInfo.FORMAT_SINGLE_NORM_FILE ? 24 : 20);
			tisFile.seek(seekpos, true);
			trace (seekpos);
			//trace ("filePosition "+tisFile.position);
			
			var termValue:String = prevTerm[1];
			trace (termValue);
			var termFieldNum:Number = prevTerm[0];
			trace (termFieldNum);
			var freqDelta:Number = prevTermInfo[1];
			trace (freqDelta);
			var proxDelta:Number = prevTermInfo[2];
			trace (proxDelta);
			
			var skipOffset:Number;
			var docFreq:Number;
			
			//trace ("prevPosition "+prevPosition);
			//trace ("indexInterval "+indexInterval);
			
			for (var count:Number = prevPosition*indexInterval+1; 
			count <= termCount && 
			(
				this._getFieldPosition(termFieldNum) < searchDicField || 
				(
					this._getFieldPosition(termFieldNum) == searchDicField &&
					termValue.localeCompare(t.text) < 0
				)
			); count++) {
				trace ("count "+count);
				trace ("termCount "+termCount);
				trace ("termFieldNum "+termFieldNum);
				trace ("searchDicField "+searchDicField);
				trace ("compare "+termValue.localeCompare(t.text));
				
				var prefixLength:Number = tisFile.readVInt();
				trace ("prefixLength "+prefixLength);
				var termSuffix:String = tisFile.readString();
				trace ("termSuffix "+termSuffix);
				trace ("filePosition "+tisFile.position);
				termFieldNum = tisFile.readVInt();
				trace ("filePosition "+tisFile.position);
				termValue = lucene.index.Term.getPrefix(termValue, prefixLength) + termSuffix;
				
				docFreq = tisFile.readVInt();
				freqDelta += tisFile.readVInt();
				proxDelta += tisFile.readVInt();
				if(docFreq >= skipInterval) {
					 skipOffset = tisFile.readVInt();
				}
				else {
					skipOffset = 0;
				}
			}
			trace ("count "+count);
			trace ("termCount "+termCount);
			trace ("termFieldNum "+termFieldNum);
			trace ("searchDicField "+searchDicField);
			trace ("compare "+termValue.localeCompare(t.text));
			
			if(termFieldNum == searchField && termValue == t.text) {
				trace ('found');
				termInfo = {'docFreq':docFreq, 'freqPointer':freqDelta, 'proxPointer':proxDelta, 'skipOffset':skipOffset, 'indexPointer':null}
			}
			else {
				termInfo = null;
			}
			
			this._termInfoCache[termKey] = termInfo;
			
			/**
			 * @todo clear termInfo cache ?
			 */
			
			return termInfo;
			
		}
		
		public function termFreqs(term:Term, shift:int):Array
		{
			var termInfo:* = this.getTermInfo(term);
			
			if (termInfo == null) {
				return new Array();
			}
			
			var frqFile:FileHandler = this.getCompoundFile(".frq");
			frqFile.seek(termInfo.freqPointer, true);
			var result:Array = new Array();
			var docId:Number = 0;
			
			for (var c:int=0; c<termInfo.docFreq; c++) {
				var docDelta:Number = frqFile.readVInt();
				if (docDelta % 2 == 1) {
					docId += (docDelta-1)/2;
					result[shift + docId] = 1;
				}
				else {
					docId += docDelta/2;
					result[shift + docId] = frqFile.readVInt();
				}
			}
			
			return result;
			
		}
		
		public function getFieldsList():Array
		{
			return this._fields;
		}
		
		public function _loadNorm(fieldNum:int):void
		{
			if (this._hasSingleNormFile) {
				var normFile:FileHandler = this.getCompoundFile('.nrm');
				
				var h:String = normFile.readMultiByte(3, "iso-8859-01");
				var hFormat:uint = normFile.readUnsignedByte();
				
				if (h == 'NRM' && hFormat != 255)
					throw new Error('Wrong format');
					
				for (var fNum:* in this._fields) {
					var fieldInfo:* = this._fields[fNum];
					if (fieldInfo.isIndexed) {
						this._norms[fNum] = normFile.readMultiByte(this._docCount, "iso-8859-01");
					}
				}			
			}
			else {
				var fFile:FileHandler = this.getCompoundFile('.f');
				this._norms[fieldNum] = fFile.readMultiByte(this._docCount, "iso-8859-01");
			}
		}
		
		public function norm (docId:int, termField:String):Number
		{
			var fieldNum:int = this.getFieldNum(termField);
			
			if ( !(this._fields[fieldNum].isIndexed) ) {
			    return null;
			}
			
			if (this._norms[fieldNum] == null) {
			    this._loadNorm(fieldNum);
			}
			
			var normFN:String = this._norms[fieldNum];
			return Similarity.decodeNorm( normFN.charCodeAt(docId) );	
		}
		
		public function getField(fieldNum:Number):Object
		{
			return this._fields[fieldNum];
		}

	}
}